/*
 * $Id: MailSenderHelper.java 45 2013-05-03 20:32:47Z gabakyan $
 * $Author: gabakyan $
 * $Revision: 45 $
 * $Date: 2013-05-03 20:32:47 +0000 (Fri, 03 May 2013) $
 *
 * Copyright (c) 2013 Supply Chain Intelligence (SCI), Inc.
 * http://www.scintelligence.com/, Email: info@scintelligence.com
 * All rights reserved.
 *
 * This file is part of Logistics Map.
 *
 * Logistics Map is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * Logistics Map is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Logistics Map.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sci.logisticsmap.service.support;

import com.sci.logisticsmap.support.Constants;
import org.apache.velocity.app.VelocityEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
//import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.ui.velocity.VelocityEngineUtils;

import javax.annotation.PostConstruct;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * The mail sender helper which provides methods for sending emails.
 *
 * @author gabakyan@gmail.com (Last modified by $Author: gabakyan $)
 * @version $Revision: 45 $
 */
@Component
public class MailSenderHelper {
    /**
     * The logger.
     */
	private static final Logger logger = LoggerFactory.getLogger(MailSenderHelper.class);

//    /**
//     * The velocity mail templates path.
//     */
//    private static final String PATH_VELOCITY_MAIL_TEMPLATES = "/velocity-templates/mail";

    /**
     * The mail sender (Java mail sender implementation).
     */
    @Autowired
    private /*MailSender*/JavaMailSenderImpl mailSender;
    /**
     * The velocity engine.
     */
    @Autowired  
    private VelocityEngine velocityEngine;

    /**
     * The mail queue.
     */
    private Queue<MailQueueEntry> mailQueue;
    /**
     * The mail send executing interval in milliseconds.
     */
    private @Value("${mailSenderHelper.mailSendExecutingIntervalInMillis}") long mailSendExecutingIntervalInMillis;

    /**
     * The mail send executor service.
     */
    private ScheduledExecutorService mailSendExecutorService;

    /**
     * Base interface for mail queue entries.
     */
    public interface MailQueueEntry {
    }

    /**
     * The simple mail queue entry class,
     * which implements {@link MailQueueEntry} interface.
     */
    public final class SimpleMailQueueEntry implements MailQueueEntry {
        /**
         * The simple mail message.
         */
        private final SimpleMailMessage mailMessage;
        /**
         * The email.
         */
        private final String email;
        /**
         * The message.
         */
        private final String message;
        /**
         * Create a new instance of the <code>SimpleMailQueueEntry</code> class.
         *
         * @param  mailMessage
         *         The mail message model
         * @param  email
         *         The recipient email
         * @param  message
         *         The message
         *
         * @throws IllegalArgumentException if the messageTemplateModel is null while the messageTemplateLocation is not null
         */
        public SimpleMailQueueEntry(SimpleMailMessage mailMessage, String email, String message) {
            this.mailMessage = mailMessage;
            this.email = email;
            this.message = message;
        }
    }

    /**
     * The velocity mail queue entry class,
     * which implements {@link MailQueueEntry} interface.
     */
    public final class VelocityMailQueueEntry implements MailQueueEntry {
        /**
         * The simple mail message.
         */
        private final SimpleMailMessage mailMessage;
        /**
         * The email.
         */
        private final String email;
        /**
         * The message temp lateLocation.
         */
        private final String messageTemplateLocation;
        /**
         * The message template model.
         */
        private final Map<String, ?> messageTemplateModel;

        /**
         * Create a new instance of the <code>VelocityMailQueueEntry</code> class.
         *
         * @param  mailMessage
         *         The mail message model
         * @param  email
         *         The recipient email
         * @param  messageTemplateLocation
         *         The message template location
         * @param  messageTemplateModel
         *         The message template model
         *
         * @throws IllegalArgumentException if the messageTemplateModel is null while the messageTemplateLocation is not null
         */
        public VelocityMailQueueEntry(SimpleMailMessage mailMessage, String email, String messageTemplateLocation, Map<String, ?> messageTemplateModel) {
            if (messageTemplateLocation != null && messageTemplateModel == null) {
                throw new IllegalArgumentException("The messageTemplateModel cannot be null while the messageTemplateLocation is not null");
            }

            this.mailMessage = mailMessage;
            this.email = email;
            this.messageTemplateLocation = messageTemplateLocation;
            this.messageTemplateModel = messageTemplateModel;
        }
    }

	/**
	 * Default constructor for <code>MailSenderHelper</code>.
	 */
    public MailSenderHelper() {
    }

    /**
     * The post construct initialization method.
     */
    @PostConstruct
    public void init() {
        mailQueue = new LinkedList<MailQueueEntry>();
        initMailSendExecutorService();
    }

    /**
     * Sends the mail.
     *
     * @param mailMessage The simple mail message.
     * @param email The email.
     * @param message The message.
     *
     * @return <code>true</code> if the message was sent, otherwise
     * <code>false</code>.
     */
    public boolean sendMail(SimpleMailMessage mailMessage, String email, String message) {
        return mailQueue.add(new SimpleMailQueueEntry(mailMessage, email, message));
    }

    /**
     * Sends the mail.
     *
     * @param mailMessage The simple mail message.
     * @param email The email.
     * @param messageTemplateLocation The message template location.
     * @param messageTemplateModel The message template model.
     *
     * @return <code>true</code> if the message was sent, otherwise
     * <code>false</code>.
     */
    public boolean sendMail(SimpleMailMessage mailMessage, String email, String messageTemplateLocation, Map<String, ?> messageTemplateModel) {
        return mailQueue.add(new VelocityMailQueueEntry(mailMessage, email, messageTemplateLocation, messageTemplateModel));
    }

    /**
     * The internal method which sends the mail.
     *
     * @param mailQueueEntry The mail queue entry.
     *
     * @return <code>true</code> if the message was sent, otherwise
     * <code>false</code>.
     */
    private boolean sendMailInternal(MailQueueEntry mailQueueEntry) {
        try{
            MimeMessage newMailMessage;
            //SimpleMailMessage newMailMessage;
            if (mailQueueEntry instanceof SimpleMailQueueEntry) {
                newMailMessage = createMailMessage((SimpleMailQueueEntry) mailQueueEntry);
            } else if (mailQueueEntry instanceof VelocityMailQueueEntry) {
                newMailMessage = createMailMessage((VelocityMailQueueEntry) mailQueueEntry);
            } else {
                throw new IllegalStateException();
            }

            mailSender.send(newMailMessage);

            return true;
        } catch(MessagingException e) {
            logger.debug("MessagingException thrown in sendMailInternal", e);
        } catch(MailException e) {
            logger.debug("MailException thrown in sendMailInternal", e);
        }

        return false;
    }

    /**
     * Creates a MIME style mail message.
     *
     * @param mailQueueEntry The simple mail queue entry.
     *
     * @return The MIME style mail message.
     */
    private MimeMessage createMailMessage(SimpleMailQueueEntry mailQueueEntry) throws MessagingException {
        return createMailMessage(mailQueueEntry.mailMessage, mailQueueEntry.email, mailQueueEntry.message);
    }

    /**
     * Creates a MIME style mail message.
     *
     * @param mailQueueEntry The velocity mail queue entry.
     *
     * @return The MIME style mail message.
     */
    private MimeMessage createMailMessage(VelocityMailQueueEntry mailQueueEntry) throws MessagingException {
        //new StringBuilder(PATH_VELOCITY_MAIL_TEMPLATES).append("/").append(mailQueueEntry.messageTemplateLocation).toString();
        String message = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, mailQueueEntry.messageTemplateLocation, mailQueueEntry.messageTemplateModel);
        return createMailMessage(mailQueueEntry.mailMessage, mailQueueEntry.email, message);
    }

    /**
     * Creates a MIME style mail message.
     *
     * @param mailMessage The simple mail message.
     * @param email The email.
     * @param message The message.
     *
     * @return The MIME style mail message.
     */
    private MimeMessage createMailMessage(SimpleMailMessage mailMessage, String email, String message) throws MessagingException {
        // Create a thread safe "copy" of the template message and customize it
        //SimpleMailMessage newMailMessage = new SimpleMailMessage(mailMessage);
        MimeMessage newMailMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(newMailMessage, true, Constants.DEFAULT_ENCODING);
        mimeMessageHelper.setFrom(mailMessage.getFrom());
        mimeMessageHelper.setTo(email);
        mimeMessageHelper.setSubject(mailMessage.getSubject());
        mimeMessageHelper.setText(message, true);
        return newMailMessage;
    }

    /**
     * Processes the mail queue entry.
     */
    private void processMailQueue() {
        MailQueueEntry mailQueueEntry;

        if ((mailQueueEntry = mailQueue.poll()) != null) {
            sendMailInternal(mailQueueEntry);
        }
    }

    /**
     * Initializes the mail send executor service.
     */
    private void initMailSendExecutorService() {
        mailSendExecutorService = Executors.newScheduledThreadPool(1);

        mailSendExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    processMailQueue();

                    Thread.sleep(mailSendExecutingIntervalInMillis);
                } catch(InterruptedException e) {
                    logger.debug("initMailSendExecutorService: an InterruptedException was thrown:", e);
                } catch(Exception e) {
                    logger.debug("initMailSendExecutorService: an Exception was thrown:", e);
                }
            }
        }, 0, mailSendExecutingIntervalInMillis, TimeUnit.MILLISECONDS);
    }

//    /*
//     * (non-Javadoc)
//     *
//     * @see Object#finalize()
//     */
//    @Override
//    protected void finalize() throws Throwable {
//        try {
//            mailSendExecutorService.shutdown();
//        } finally {
//            super.finalize();
//        }
//    }
}
